document.addEventListener('DOMContentLoaded', function() { // DOM Elements const widget = document.getElementById('streamer-widget'); const collapseBtn = document.getElementById('collapse-btn'); const widgetContent = document.getElementById('widget-content'); const streamersContainer = document.getElementById('streamers-container'); const streamEmbed = document.getElementById('stream-embed'); const emptyState = document.getElementById('empty-state'); const liveCountBadge = document.getElementById('live-count'); // Exit if essential elements are not found if (!widget || !streamersContainer || !liveCountBadge) { console.error('Essential DOM elements not found for streamer widget'); return; } // Widget state let isCollapsed = false; let lastFetchTime = 0; const FETCH_INTERVAL = 60000; // 1 minute let currentEmbeddedStream = null; let currentEmbeddedPlatform = null; let isWidgetVisible = false; let autoplayedStreams = new Set(); // Track autoplay to avoid repeats let hasInitialized = false; // Track if widget has been initialized // Initialize widget as hidden initially if (widget) { widget.classList.add('hidden'); } // Ensure modal is closed on page load if (streamEmbed) { streamEmbed.style.display = 'none'; streamEmbed.classList.add('hidden'); } // Initialize empty state as hidden if (emptyState) { emptyState.classList.add('hidden'); emptyState.style.display = 'none'; } // Device detection const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); // Function to toggle between expanded/collapsed states function toggleCollapse() { isCollapsed = !isCollapsed; if (isCollapsed) { widget.classList.add('widget-collapsed'); widgetContent.classList.add('hidden'); collapseBtn.innerHTML = ` `; } else { widget.classList.remove('widget-collapsed'); widgetContent.classList.remove('hidden'); collapseBtn.innerHTML = ` `; } } // Add click event to collapse button collapseBtn.addEventListener('click', toggleCollapse); // Set initial state based on device function applyMobileState() { if (isMobile && isWidgetVisible) { isCollapsed = true; widget.classList.add('widget-collapsed'); widgetContent.classList.add('hidden'); collapseBtn.innerHTML = ` `; } } // Functions to show/hide the entire widget function showWidget() { if (widget && !isWidgetVisible) { widget.classList.remove('hidden'); isWidgetVisible = true; setTimeout(applyMobileState, 100); } } function hideWidget() { if (widget && isWidgetVisible) { widget.classList.add('hidden'); isWidgetVisible = false; } } // Function to format viewer count function formatViewerCount(count) { if (count >= 1000000) { return (count / 1000000).toFixed(1) + 'M'; } else if (count >= 1000) { return (count / 1000).toFixed(1) + 'K'; } return count; } // Function to format start time function formatStartTime(startedAt) { const startTime = new Date(startedAt); const now = new Date(); const diffMs = now - startTime; const diffMins = Math.floor(diffMs / 60000); if (diffMins < 60) { return `${diffMins}min`; } else { const hours = Math.floor(diffMins / 60); return `${hours}h${diffMins % 60 > 0 ? ' ' + (diffMins % 60) + 'min' : ''}`; } } // Function to create stream embed with autoplay function createStreamEmbed(streamer) { if (isMobile) return; // Don't create embed on mobile currentEmbeddedStream = streamer.channel_id; currentEmbeddedPlatform = streamer.platform; const embedContent = document.querySelector('.embed-responsive'); embedContent.innerHTML = ''; let embedElement; if (streamer.platform === 'twitch') { // Get domain name for parent parameter const hostname = window.location.hostname; const domain = hostname.replace(/^www\./, ''); // Remove www prefix if present embedElement = ` `; } else if (streamer.platform === 'youtube') { // YouTube embed with autoplay and low quality embedElement = ` `; } else if (streamer.platform === 'tiktok') { // TikTok doesn't support iframe embeds for live streams yet // Open in new tab instead window.open(`https://www.tiktok.com/@${streamer.channel_id}/live`, '_blank'); return; } embedContent.innerHTML = embedElement; streamEmbed.style.display = 'flex'; streamEmbed.classList.remove('hidden'); } // Function to close stream embed function closeStreamEmbed() { if (streamEmbed) { streamEmbed.style.display = 'none'; streamEmbed.classList.add('hidden'); } currentEmbeddedStream = null; currentEmbeddedPlatform = null; const embedContent = document.querySelector('.embed-responsive'); if (embedContent) { embedContent.innerHTML = ''; } } // Add close button listener const closeEmbedBtn = document.getElementById('close-embed'); if (closeEmbedBtn) { closeEmbedBtn.addEventListener('click', closeStreamEmbed); } // Add click outside to close modal if (streamEmbed) { streamEmbed.addEventListener('click', function(e) { // Close if clicking on the overlay (not the content) if (e.target === streamEmbed) { closeStreamEmbed(); } }); } // Add escape key to close modal document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && currentEmbeddedStream) { closeStreamEmbed(); } }); // Function to auto-play first stream on desktop function autoplayFirstStream(streamers) { // Since we now have iframes directly in cards on desktop, // we don't need to auto-open the modal embed // The streams will auto-play directly in their cards console.log(`${streamers.length} live streams loaded`); } // Function to create streamer cards function createStreamerCard(streamer) { // Handle thumbnail for different platforms let thumbnailUrl; if (streamer.platform === 'youtube') { thumbnailUrl = streamer.thumbnail_url || 'https://placehold.co/320x180/ff0000/ffffff?text=YouTube'; } else if (streamer.platform === 'tiktok') { thumbnailUrl = streamer.thumbnail_url || 'https://placehold.co/320x180/000000/ffffff?text=TikTok'; } else { thumbnailUrl = streamer.thumbnail_url ? streamer.thumbnail_url.replace('{width}', '320').replace('{height}', '180') : 'https://placehold.co/320x180/6441a5/ffffff?text=Twitch'; } const card = document.createElement('div'); card.className = 'streamer-card-enhanced bg-gradient-to-br from-white to-gray-50 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden border border-gray-100 hover:border-purple-200'; // Platform specific styles and links let platformClass, streamUrl, platformIcon; if (streamer.platform === 'youtube') { platformClass = 'bg-gradient-to-r from-red-500 to-red-600'; streamUrl = `https://youtube.com/channel/${streamer.channel_id}`; platformIcon = ''; } else if (streamer.platform === 'tiktok') { platformClass = 'bg-gradient-to-r from-black to-gray-800'; streamUrl = `https://www.tiktok.com/@${streamer.channel_id}/live`; platformIcon = ''; } else { platformClass = 'bg-gradient-to-r from-purple-500 to-indigo-600'; streamUrl = `https://twitch.tv/${streamer.channel_id}`; platformIcon = ''; } // Icons for badges const viewIcon = ''; const gameIcon = ''; // Always open in new tab for both mobile and desktop const linkTarget = 'target="_blank"'; card.innerHTML = `
${!isMobile ? `
${streamer.platform === 'twitch' ? ` ` : streamer.platform === 'youtube' ? ` ` : ` ${streamer.display_name} `}
` : ` ${streamer.display_name} `}
${platformIcon} ${streamer.platform}
${platformIcon.replace('h-3 w-3', 'h-5 w-5')}
LIVE

${streamer.display_name}

${streamer.title || 'No title available'}

${viewIcon} ${formatViewerCount(streamer.viewer_count || 0)}
${streamer.game_name ? `
${gameIcon} ${streamer.game_name}
` : ''}
`; // Allow normal link navigation to platform for both desktop and mobile // No need for preventDefault - let the browser handle the link naturally return card; } // Make createStreamEmbed global for inline onclick window.createStreamEmbed = createStreamEmbed; // Function to fetch streamer status async function fetchStreamers() { const currentTime = Date.now(); if (currentTime - lastFetchTime < FETCH_INTERVAL && lastFetchTime !== 0) { return; } try { const response = await fetch('/api/getStreamStatus'); const data = await response.json(); lastFetchTime = currentTime; const allLiveStreamers = []; // Process Twitch streamers if (data.status.twitchStreamers && Array.isArray(data.status.twitchStreamers)) { const twitchStreamers = data.status.twitchStreamers .filter(streamer => { const isLive = streamer.is_live == 1 || streamer.is_live === "1" || streamer.is_live === true; return isLive; }) .map(streamer => ({ ...streamer, platform: 'twitch', display_name: streamer.channel_id, title: streamer.last_title, started_at: streamer.api_started_at || streamer.current_session_start, thumbnail_url: `https://static-cdn.jtvnw.net/previews-ttv/live_user_${streamer.channel_id}-320x180.jpg` })); allLiveStreamers.push(...twitchStreamers); } // Process YouTube streamers if (data.status.youtubeStreamers && Array.isArray(data.status.youtubeStreamers)) { const youtubeStreamers = data.status.youtubeStreamers .filter(streamer => { const isLive = streamer.is_live === 1 || streamer.is_live === "1"; return isLive; }) .map(streamer => ({ ...streamer, platform: 'youtube', display_name: streamer.channel_id, title: streamer.last_title, started_at: streamer.api_started_at || streamer.current_session_start, thumbnail_url: streamer.thumbnail_url || 'https://placehold.co/320x180/ff0000/ffffff?text=YouTube' })); allLiveStreamers.push(...youtubeStreamers); } // Process TikTok streamers if (data.status.tiktokStreamers && Array.isArray(data.status.tiktokStreamers)) { const tiktokStreamers = data.status.tiktokStreamers .filter(streamer => { const isLive = streamer.is_live === 1 || streamer.is_live === "1" || streamer.is_live === true; return isLive; }) .map(streamer => ({ ...streamer, platform: 'tiktok', display_name: streamer.channel_id, title: streamer.last_title || 'TikTok Live', started_at: streamer.api_started_at || streamer.current_session_start, thumbnail_url: streamer.thumbnail_url || 'https://placehold.co/320x180/000000/ffffff?text=TikTok+Live' })); allLiveStreamers.push(...tiktokStreamers); } // Update live count const totalLiveCount = allLiveStreamers.length; liveCountBadge.textContent = totalLiveCount; // Clear container streamersContainer.innerHTML = ''; if (totalLiveCount > 0) { showWidget(); hasInitialized = true; // Ensure empty state is hidden when we have streams if (emptyState) { emptyState.classList.add('hidden'); emptyState.style.display = 'none'; } if (streamersContainer) { streamersContainer.classList.remove('hidden'); streamersContainer.style.display = 'block'; } // Sort streamers by viewer count const sortedStreamers = [...allLiveStreamers].sort((a, b) => (b.viewer_count || 0) - (a.viewer_count || 0) ); // Create and add cards sortedStreamers.forEach(streamer => { const card = createStreamerCard(streamer); streamersContainer.appendChild(card); }); // Autoplay first stream on desktop autoplayFirstStream(sortedStreamers); } else { // Only hide widget after first successful initialization // This prevents the widget from disappearing during temporary API issues if (hasInitialized) { // Show empty state instead of hiding completely if (isWidgetVisible) { if (emptyState) { emptyState.classList.remove('hidden'); emptyState.style.display = 'block'; } if (streamersContainer) { streamersContainer.classList.add('hidden'); streamersContainer.style.display = 'none'; } closeStreamEmbed(); } } else { // First load with no streamers - hide widget completely hideWidget(); closeStreamEmbed(); } } } catch (error) { console.error('Error fetching streamers:', error); // Don't hide widget on API errors to prevent flickering // Only hide if this is the first load and widget hasn't been initialized if (!hasInitialized) { hideWidget(); } // Always close embed on error to prevent issues closeStreamEmbed(); } } // Fetch streamers on page load fetchStreamers(); // Set up polling to update every minute setInterval(fetchStreamers, FETCH_INTERVAL); });